package com.maiko.usercenter.aop;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.UUID;

/**
 * 请求日志切面拦截器
 * <p>
 * 该切面拦截 controller 包下所有接口请求，自动打印接口调用的开始、参数、耗时等信息，
 * 用于接口性能分析、审计记录、错误排查等场景。
 *
 * <p>使用方式：不需要手动调用，只要接口在 com.maiko.usercenter.controller 包下，
 * 每次请求就会自动触发该日志打印逻辑。
 *
 * <p>日志输出格式：
 * <ul>
 *     <li>request start，id: uuid, path: /xxx, ip: 127.0.0.1, params: [arg1, arg2]</li>
 *     <li>request end, id: uuid, cost: 37ms</li>
 * </ul>
 *
 * @author Maiko7
 * @create 2025-07-15 09:53
 */
@Aspect
@Component
@Slf4j
public class LogInterceptor {

    /**
     * 环绕通知：拦截 controller 包下所有方法，记录请求参数和执行耗时
     *
     * | 部分                                  | 含义                          | 示例说明                                     |
     * | ----------------------------------- | --------------------------- | ---------------------------------------- |
     * | `execution(...)`                    | Spring AOP 的执行切点语法，表示拦截方法调用 | 固定格式                                     |
     * | `*`                                 | 任意返回类型                      | 可以是 void、String、Result 等                 |
     * | `com.maiko.usercenter.controller.*` | controller 包下的所有类           | 如：`UserController`、`LoginController` 等   |
     * | `*`                                 | 所有方法                        | 如：`login()`、`logout()`、`getUserById()` 等 |
     * | `(..)`                              | 任意参数                        | 可以有任意个参数，也可以无参数                          |
     *
     * @param point 切入点，表示被拦截的方法调用
     * @return 方法的返回结果
     * @throws Throwable 如果原方法抛出异常，则继续抛出
     */
    @Around("execution(* com.maiko.usercenter.controller.*.*(..))")
    public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
        // 1. 开始计时
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 2. 获取请求上下文与信息
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();

        // 3. 生成唯一请求 ID，用于日志追踪
        String requestId = UUID.randomUUID().toString();
        String url = httpServletRequest.getRequestURI();

        // 4. 获取请求参数
        Object[] args = point.getArgs();
        String reqParam = "[" + StringUtils.join(args, ", ") + "]";

        // 5. 打印请求日志
        log.info("request start，id: {}, path: {}, ip: {}, params: {}", requestId, url,
                httpServletRequest.getRemoteHost(), reqParam);

        // 6. 执行原方法
        Object result = point.proceed();

        // 7. 停止计时并打印响应日志
        stopWatch.stop();
        long totalTimeMillis = stopWatch.getTotalTimeMillis();
        log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);

        return result;
    }
}
